import pandas as pd
pd.set_option('display.expand_frame_repr', False)
import matplotlib
import matplotlib.pyplot as plt
font = {'size' : 20}
matplotlib.rc('font', **font)
import seaborn as sns
from pylab import rcParams
rcParams["figure.figsize"] = 30,16
import sklearn
from sklearn import metrics
import numpy as np
import scipy
import datetime as dt
from datetime import date
from collections import OrderedDict
from sklearn.preprocessing import PolynomialFeatures
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import warnings
warnings.filterwarnings("ignore")
import sys
sys.path.insert(0, "../")
import functions
#Daten aus CSV-Datei laden
df = pd.read_csv("data.csv", index_col=0, parse_dates=True)
df.index.freq = "D"
df_ = pd.read_csv("Datenbeschaffung/kalender.csv", index_col=0, parse_dates=True, usecols=[0,2])
df_ = df_.join(pd.read_csv("Datenbeschaffung/stuttgart.csv", index_col=0, parse_dates=True, usecols=[0,2], squeeze=True).rename("stuttgart"))
df_ = df_.join(pd.read_csv("Datenbeschaffung/freiburg.csv", index_col=0, parse_dates=True, usecols=[0,2], squeeze=True).rename("freiburg"))
df_ = df_.join(pd.read_csv("Datenbeschaffung/mannheim.csv", index_col=0, parse_dates=True, usecols=[0,2], squeeze=True).rename("mannheim"))
df_ = df_.join(pd.read_csv("Datenbeschaffung/ulm.csv", index_col=0, parse_dates=True, usecols=[0,2], squeeze=True).rename("ulm"))
#Durchschnitt berechnen
df["temperatur"] = round(((df_["stuttgart"] + df_["freiburg"] + df_["mannheim"] + df_["ulm"]) / 4), 1)
#Temporär für Analysen verwendete Spalten berechnen
df["temperatur_gerundet"] = round(df["temperatur"],0)
df["temperatur_zusammengefasst"] = 3 * round(df["temperatur"]/3)
#Aufteilung in zwei DataFrames für weitere Analysen
df_arbeitstag = df[df["arbeitstag"] == 1]
df_arbeitsfreiertag = df[df["arbeitstag"] == 0]
print(df)
print()
print(df.describe().transpose())
verbrauch monat wochentag arbeitstag temperatur temperatur_gerundet temperatur_zusammengefasst
datum
2015-01-01 126197 Januar Donnerstag 0 -2.5 -2.0 -3.0
2015-01-02 147085 Januar Freitag 1 -0.0 -0.0 -0.0
2015-01-03 141426 Januar Samstag 0 1.2 1.0 0.0
2015-01-04 132446 Januar Sonntag 0 -0.2 -0.0 -0.0
2015-01-05 152611 Januar Montag 1 -0.5 -0.0 -0.0
... ... ... ... ... ... ... ...
2021-12-27 153489 Dezember Montag 1 5.7 6.0 6.0
2021-12-28 155182 Dezember Dienstag 1 8.2 8.0 9.0
2021-12-29 153611 Dezember Mittwoch 1 8.7 9.0 9.0
2021-12-30 148126 Dezember Donnerstag 1 11.3 11.0 12.0
2021-12-31 137327 Dezember Freitag 1 10.7 11.0 12.0
[2557 rows x 7 columns]
count mean std min 25% 50% 75% max
verbrauch 2557.0 169329.063355 27116.871253 102469.0 147992.0 175584.0 190001.0 231190.0
arbeitstag 2557.0 0.687133 0.463751 0.0 0.0 1.0 1.0 1.0
temperatur 2557.0 11.706492 7.845246 -10.0 5.2 11.2 18.0 32.1
temperatur_gerundet 2557.0 11.688307 7.842557 -10.0 5.0 11.0 18.0 32.0
temperatur_zusammengefasst 2557.0 11.737192 7.883477 -9.0 6.0 12.0 18.0 33.0
Die Temperatur folgt einem klaren, jährlichen Muster. Im Frühling erreichen die Temperaturen von etwa +5°C bis auf +15°C, im Sommer reichen sie von +10°C bis zu +30 °C in der Spitze, im Herbst von +5°C bis +20 °C und im Winter lediglich von -5°C bis etwa +10°C. Dieser Verlauf wiederholt sich jedes Jahr.
functions.draw_years(df)
#Temperatur plotten
plt.plot(df["temperatur"], label="Temperatur");
plt.plot(df["temperatur"].rolling(window=14).mean(), color="red", label="Gleitender Durchschnitt über 14 Tage")
#Achsen und Layout
plt.title("Temperatur (2015 - 2021)")
plt.ylabel("Temperatur in °C")
plt.ylim(-10, 35)
plt.xlabel("Datum")
plt.xlim(dt.datetime(2015,1,1), dt.datetime(2021,12,31))
#Legende einfügen
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc="lower left", bbox_to_anchor=(0,-0.15), ncol=4)
plt.show()
Die Boxplots nach Monaten zeigen noch einmal die jährliche Saisonalität.
#Boxplot erstellen
sns.boxplot(data=df, x="monat", y="temperatur")
#Achsen und Layout
plt.title("Temperatur nach Monaten")
plt.ylabel("Temperatur in °C")
plt.ylim(-10, 35)
plt.xlabel("Monat")
plt.show()
Es handelt sich um einigermaßen um den Mittelwert normalverteilte Daten.
#Histogramm erstellen
sns.countplot(df["temperatur_gerundet"], order=np.arange(-10,33,1));
#Achsen und Layout
plt.title("Histogramm der Temperaturwerte")
plt.ylabel("Anzahl")
plt.xlabel("Temperatur in °C")
plt.xticks(np.arange(0, 43, 2))
plt.show()
Die Temperatur scheint dem Stromverbrauch zunächst entgegenzulaufen. Mit steigender Temperatur fällt der Stromverbrauch. Allerdings ist auch erkennbar, dass der Stromverbrauch Mitte des Jahres, wenn die Temperatur ihre Höchststände erreicht, ebenfalls wieder ansteigt.
fig, ax1 = plt.subplots()
functions.draw_years(df)
ax1.set_title("Stromverbrauch und Temperatur")
#Verbrauch plotten
ax1.plot(df["verbrauch"], color="red", label="Stromverbrauch")
#Achsen und Layout
ax1.set_ylabel("Stromverbrauch in MWh", color="red")
ax1.tick_params(axis="y", labelcolor="red")
ax1.set_ylim(100000,235000)
ax1.set_xlabel("Datum")
ax1.set_xlim(dt.datetime(2015,1,1), dt.datetime(2021,12,31))
#Legende einfügen
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc="lower left", bbox_to_anchor=(0,-0.15), ncol=4)
#Tagesdurchschnittstemperatur plotten
ax2 = ax1.twinx()
ax2.plot(df["temperatur"], color="blue", label="Temperatur")
#Achsen und Layout
ax2.set_ylabel("Temperatur in °C", color="blue")
ax2.tick_params(axis="y", labelcolor="blue")
ax2.set_ylim(-10, 35)
ax2.set_xlim(dt.datetime(2015,1,1), dt.datetime(2021,12,31))
#Legende einfügen
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc="lower left", bbox_to_anchor=(0,-0.2), ncol=4)
plt.tight_layout()
plt.show()
#Korrelationskoeffizienten ausgeben
functions.correlation_coefficients(df["temperatur"], df["verbrauch"])
print("\n\n")
Pearson : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.325 Spearman : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.374 Kendall : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.267
#Durchlaufen verschiedener gleitender Durchschnitte
for window in [3, 7, 14, 28]:
print("\n\n", "Gleitender Durchschnitt über", window,"Tage----------------------------------------------------------------------------------------------------------------------------------------------\n\n")
fig, ax1 = plt.subplots()
functions.draw_years(df)
ax1.set_title("Stromverbrauch und Temperatur (Gleitender Durchschnitt über " + str(window) + " Tage)")
#Gleitenden Durchschnitt des Verbrauchs plotten
ax1.plot(df["verbrauch"].rolling(window=window).mean(), color="red", label="Stromverbrauch (Gleitender Durchschnitt " + str(window) + " Tage)")
#Achsen und Layout
ax1.set_ylabel("Stromverbrauch in MWh", color="red")
ax1.tick_params(axis="y", labelcolor="red")
ax1.set_ylim(100000,235000)
ax1.set_xlabel("Datum")
ax1.set_xlim(dt.datetime(2015,1,1), dt.datetime(2021,12,31))
#Legende einfügen
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc="lower left", bbox_to_anchor=(0,-0.15), ncol=4)
#Gleitenden Durchschnitt der Temperatur plotten
ax2 = ax1.twinx()
ax2.plot(df["temperatur"].rolling(window=window).mean(), color="blue", label="Temperatur (Gleitender Durchschnitt " + str(window) + " Tage)")
#Achsen und Layout
ax2.set_ylabel("Temperatur in °C", color="blue")
ax2.tick_params(axis="y", labelcolor="blue")
ax2.set_ylim(-10, 35)
ax2.set_xlim(dt.datetime(2015,1,1), dt.datetime(2021,12,31))
#Legende einfügen
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc="lower left", bbox_to_anchor=(0,-0.2), ncol=4)
plt.tight_layout()
plt.show()
#Korrelationskoeffizienten ausgeben
functions.correlation_coefficients(df["temperatur"].rolling(window=window).mean().dropna(), df["verbrauch"].rolling(window=window).mean().dropna())
print("\n\n")
Gleitender Durchschnitt über 3 Tage----------------------------------------------------------------------------------------------------------------------------------------------
Pearson : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.415 Spearman : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.421 Kendall : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.288 Gleitender Durchschnitt über 7 Tage----------------------------------------------------------------------------------------------------------------------------------------------
Pearson : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.576 Spearman : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.612 Kendall : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.445 Gleitender Durchschnitt über 14 Tage----------------------------------------------------------------------------------------------------------------------------------------------
Pearson : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.619 Spearman : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.633 Kendall : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.463 Gleitender Durchschnitt über 28 Tage----------------------------------------------------------------------------------------------------------------------------------------------
Pearson : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.672 Spearman : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.677 Kendall : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.487
Auch hier zeit sich, dass der Stromverbrauch bis zu Temperaturen von etwa 20°C fällt, dann stagniert und ab circa 24°C dann wieder ansteigt. Die gilt sowohl für Arbeits- wie auch für arbeitsfreie Tage.
x = range(-10, 36, 1)
#Temperatur und Verbrauch an Arbeitstagen plotten
plt.scatter(df_arbeitstag["temperatur"], df_arbeitstag["verbrauch"], color="red", label="Arbeitstag")
#Steigungen a1 und b1 und Intercept c1 für Regression bilden (a1 + x² + b1 * x + c1)
a1, b1, c1 = np.polyfit(df_arbeitstag["temperatur"], df_arbeitstag["verbrauch"], 2)
#Regressionsfunktion einzeichnen
plt.plot(x, (a1 * np.square(x) + b1 * x + c1), color="red", linewidth=2.5, label="Arbeitstag (Regression)")
#Temperatur und Verbrauch an arbeitsfreien Tagen plotten
plt.scatter(df_arbeitsfreiertag["temperatur"], df_arbeitsfreiertag["verbrauch"], color="blue", label="Arbeitsfreier Tag")
#Steigungen a2 und b2 und Intercept c2 für Regression bilden (a2 + x² + b2 * x + c2)
a2, b2, c2 = np.polyfit(df_arbeitsfreiertag["temperatur"], df_arbeitsfreiertag["verbrauch"], 2)
#Regressionsfunktion einzeichnen
plt.plot(x, (a2 * np.square(x) + b2 * x + c2), color="blue", linewidth=2.5, label="Arbeitsfreier Tag (Regression)")
#Steigungen a3 und b3 und Intercept c3 für Regression bilden (a3 + x² + b3 * x + c3)
a3, b3, c3 = np.polyfit(df["temperatur"], df["verbrauch"], 2)
#Regressionsfunktion einzeichnen
plt.plot(x, (a3 * np.square(x) + b3 * x + c3), color="green", linewidth=2.5, label="Gesamt (Regression)")
#Achsen und Layout
plt.title("Stromverbrauch nach Temperatur (Scatterplot)")
plt.ylabel("Stromverbrauch in MWh")
plt.ylim(100000, 235000)
plt.xlabel("Temperatur in °C")
plt.xlim(-10, 35)
#Legende einfügen
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc="lower left", bbox_to_anchor=(0,-0.2), ncol=4)
plt.show()
#Korrelationskoeffizienten ausgeben
print("Arbeitstage")
functions.correlation_coefficients(df_arbeitstag["temperatur"], df_arbeitstag["verbrauch"])
print("\n\nArbeitsfreie Tage")
functions.correlation_coefficients(df_arbeitsfreiertag["temperatur"], df_arbeitsfreiertag["verbrauch"])
Arbeitstage Pearson : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.554 Spearman : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.635 Kendall : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.462 Arbeitsfreie Tage Pearson : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.558 Spearman : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.55 Kendall : p-Wert von 0.0 -> Nullhypothese wird abgelehnt: Die Daten korrelieren mit einem Koeffizienten von -0.38
Auch hier zeit sich, dass der Stromverbrauch bis zu Temperaturen von etwa 20°C fällt, dann stagniert und ab circa 24°C dann wieder ansteigt. Je niedriger die Temperaturen sind, desto mehr Energie und damit auch Strom wird bspw. für das Heizen benötigt. Mit steigenden Temperaturen fällt zwar dieser Strombedarf, umgedreht wird allerdings mehr Energie für Kühlengen etc. benötigt. Ab einer Temperatur zwischen 20°C und 24°C gleichen die Einsparungen beim Heizen den Mehrbedarf bei Kühlungen nicht mehr aus und der Strombedarf steigt wieder an.
#Boxplot der gerundeten Temperaturen
sns.boxplot(data=df, x="temperatur_gerundet", y="verbrauch", hue="arbeitstag")
#Achsen und Layout
plt.title("Stromverbrauch nach gerundeter Temperatur (Boxplot)")
plt.ylabel("Stromverbrauch in MWh")
plt.ylim(100000,235000)
plt.xlabel("Monat")
plt.xticks(np.arange(0, 43, 2))
plt.show()
#Boxplot der zusammengefassten Temperaturen
sns.boxplot(data=df, x="temperatur_zusammengefasst", y="verbrauch", hue="arbeitstag")
#Achsen und Layout
plt.title("Stromverbrauch nach gruppierter Temperatur (Boxplot)")
plt.ylabel("Stromverbrauch in MWh")
plt.ylim(100000,235000)
plt.xlabel("Monat")
plt.show()
Durch die Regressionsanalyse wird geprüft, inwieweit sich der Verlauf des Stromverbrauchs anhand der verfügbaren exogenen Merkmale modellieren lässt. Es geht dabei noch nicht um die Erstellung eines Vorhersagemodells. Stattdessen wird die Regressionsanalyse eher mit Blick auf potenzielle Zusammenhänge, Korrelationen und Muster beziehungsweise generelle Verläufe durchgeführt. Aus diesem Grund bietet sich eine Funktion sechsten Grades an. Dafür wird mit den Daten von 2015 bis 2018 und den entsprechenden Merkmalen eine Regression sechsten Grades durchgeführt, welche dann mit den Daten für 2019 getestet wird. Wie bereits erwähnt, gibt es 2020 und 2021 Abweichungen vom ansonsten üblichen Verlauf. Daher werden nur die Daten bis einschließlich 2019 verwendet.
Die Regression auf die Temperatur zeigt, dass sich hierdurch zumindest die jährliche Aufwärts- und Abwärtsbewegung abbilden lässt. Da allerdings Informationen über die Wochentage fehlen, kann die wöchentliche Saisonalität selbstverständlich nicht erfasst werden.
#Für Regression relevante Daten herausfiltern
df_regression = pd.DataFrame(data=[df["verbrauch"], df["temperatur"]])
df_regression = df_regression.transpose()
df_regression = df_regression["2015-01-01":"2019-12-31"]
print(df_regression)
#Aufteilen in Trainings- und Testdaten
train, test = functions.train_test_split(df_regression, 365)
#Erstellen der polynomialen Merkmale
pf = PolynomialFeatures(degree=6)
pf = pf.fit(df_regression[["temperatur"]].values.reshape(-1, 1))
#Aufteilung in X und y
y_train = train["verbrauch"]
y_test = test["verbrauch"]
X_train = pf.transform(train[["temperatur"]])
X_test = pf.transform(test[["temperatur"]])
#Regressionsmodell erstellen
model = LinearRegression().fit(X_train, y_train)
#Vorhersagen erstellen
preds = model.predict(X_test)
preds = pd.Series(data=preds, index=pd.date_range('01/01/2019', periods=365, freq='D')).rename("vorhergesagter verbrauch")
#Vorhersagen auswerten
functions.custom_metrics(y_test, preds)
verbrauch temperatur
datum
2015-01-01 126197.0 -2.5
2015-01-02 147085.0 -0.0
2015-01-03 141426.0 1.2
2015-01-04 132446.0 -0.2
2015-01-05 152611.0 -0.5
... ... ...
2019-12-27 129551.0 5.1
2019-12-28 125395.0 1.7
2019-12-29 121331.0 1.1
2019-12-30 137130.0 2.8
2019-12-31 131060.0 3.2
[1826 rows x 2 columns]
Vorhersage
R2 0.1
MAE 20706.7
MSE 598421708.1
RMSE 24462.7
MAPE 14.0 %
Mittels Regression auf die Temperatur und den Indikator für Arbeitstage lässt sich sowohl die jährliche Auf- und Abwärtsbewegung wie auch die wöchentliche Saisonalität abbilden. Das Modell ist allerdings Anfang des Jahres leicht nach unten und Mitte/Ende des Jahres leicht nach oben verzerrt.
#Für Regression relevante Daten herausfiltern
df_regression = pd.DataFrame(data=[df["verbrauch"], df["arbeitstag"], df["temperatur"]])
df_regression = df_regression.transpose()
df_regression = df_regression["2015-01-01":"2019-12-31"]
print(df_regression)
#Aufteilen in Trainings- und Testdaten
train, test = functions.train_test_split(df_regression, 365)
#Erstellen der polynomialen Merkmale
pf = PolynomialFeatures(degree=6)
pf = pf.fit(df_regression[["arbeitstag", "temperatur"]].values.reshape(-2, 2))
#Aufteilung in X und y
y_train = train["verbrauch"]
y_test = test["verbrauch"]
X_train = pf.transform(train[["arbeitstag", "temperatur"]])
X_test = pf.transform(test[["arbeitstag", "temperatur"]])
#Regressionsmodell erstellen
model = LinearRegression().fit(X_train, y_train)
#Vorhersagen erstellen
preds = model.predict(X_test)
preds = pd.Series(data=preds, index=pd.date_range('01/01/2019', periods=365, freq='D')).rename("vorhergesagter verbrauch")
#Vorhersagen auswerten
functions.custom_metrics(y_test, preds)
verbrauch arbeitstag temperatur
datum
2015-01-01 126197.0 0.0 -2.5
2015-01-02 147085.0 1.0 -0.0
2015-01-03 141426.0 0.0 1.2
2015-01-04 132446.0 0.0 -0.2
2015-01-05 152611.0 1.0 -0.5
... ... ... ...
2019-12-27 129551.0 1.0 5.1
2019-12-28 125395.0 0.0 1.7
2019-12-29 121331.0 0.0 1.1
2019-12-30 137130.0 1.0 2.8
2019-12-31 131060.0 1.0 3.2
[1826 rows x 3 columns]
Vorhersage
R2 0.8
MAE 8106.5
MSE 145835413.0
RMSE 12076.2
MAPE 5.4 %
Insgesamt zeigt sich, dass der Stromverbrauch neben dem Indikator für Arbeitstage auch stark von der Temperatur abhängt. Die Temperatur wird daher für das weitere Modeling verwendet.
Die Temperatur hat einen erkennbaren und direkten Einfluss auf dem Stromverbrauch und kann für Vorhersagen genutzt werden.
Die als relevant und aussagekräftig ermittelten oder für Analysen relevanten Daten werden in der Datei „data.csv“ zwischengespeichert.
df = df[["verbrauch", "monat", "wochentag", "arbeitstag", "temperatur"]]
print(df)
df.to_csv("data.csv")
verbrauch monat wochentag arbeitstag temperatur datum 2015-01-01 126197 Januar Donnerstag 0 -2.5 2015-01-02 147085 Januar Freitag 1 -0.0 2015-01-03 141426 Januar Samstag 0 1.2 2015-01-04 132446 Januar Sonntag 0 -0.2 2015-01-05 152611 Januar Montag 1 -0.5 ... ... ... ... ... ... 2021-12-27 153489 Dezember Montag 1 5.7 2021-12-28 155182 Dezember Dienstag 1 8.2 2021-12-29 153611 Dezember Mittwoch 1 8.7 2021-12-30 148126 Dezember Donnerstag 1 11.3 2021-12-31 137327 Dezember Freitag 1 10.7 [2557 rows x 5 columns]